package ai.cultural;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

/**
 * 文化算法，解决非线性无约束函数优化问题
 */
public class CulturalAlgorithm {
    private int maxGeneration; // 最大进化代数
    private int populationSize; // 种群规模
    private int variableNum; // 变量数目，也就是适应值函数有几个参数
    private double voteRate; // 参与投票样本数占父子两代总样本数的比例
    private int votePopulationSize; // 参与投票样本数，根据voteRate计算得出，这些样本是随机抽出的
    private double acceptRate; // 接受函数的接受率
    private double factor;
    private double[] domainMin; // 变量定义域最小值数组，元素个数=variableNum
    private double[] domainMax; // 变量定义域最大值数组，元素个数=variableNum

    private double population[][];    //种群空间
    private double childPopulation[][];    //子代种群
    private double bothPopulation[][]; // 父子两代组成的种群空间
    private double situation[]; // 信仰空间
    private double fitValue; // 最优解
    private int currentGeneration = 1; // 当前代，从1开始编号

    public CulturalAlgorithm(int maxGeneration, int populationSize, int variableNum, double voteRate, double acceptRate,
                             double factor, double[] domainMin, double[] domainMax) {
        this.maxGeneration = maxGeneration;
        this.populationSize = populationSize;
        this.variableNum = variableNum;
        this.voteRate = voteRate;
        this.acceptRate = acceptRate;
        this.factor = factor;
        this.domainMin = domainMin;
        this.domainMax = domainMax;

        population = new double[variableNum][populationSize];
        childPopulation = new double[variableNum][populationSize];
        bothPopulation = new double[variableNum][2 * populationSize];
        situation = new double[variableNum];
        votePopulationSize = (int) (2 * populationSize * this.voteRate);
    }

    public void compute() {
        double normMin[] = Arrays.copyOf(domainMin, domainMin.length);  //记录每个变量的搜索下限，初始为定义域下限数组
        double normMax[] = Arrays.copyOf(domainMax, domainMax.length);  //记录每个变量的搜索上限，初始为定义域上限数组
        double L[] = {100000.0, 100000.0};
        double U[] = {100000.0, 100000.0};
        int winNum[][] = new int[variableNum][2 * populationSize];    //用于父子个体比较，记录胜利次数
        initPopulation(population, domainMin, domainMax); // 初始化初代种群
        for (int p = 0; p < variableNum; p++) {
            System.out.println(String.format("*************第%s代，第%s个参数************", currentGeneration, p));
            for (int j = 0; j < populationSize; j++) {
                System.out.println(population[p][j] + " " + func(population, j));
            }
            System.out.println();
        }
        initBelief(situation, population); // 初始化信仰空间
        for (int i = 0; i < maxGeneration; i++) {
            currentGeneration++;
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            for (int p = 0; p < variableNum; p++) {
                for (int j = 0; j < populationSize; j++) {   //影响函数指导下产生子代
                    double step = normMax[p] - normMin[p];
                    double rand;
                    if (step != 0) {
                        if (population[p][j] < normMin[p]) {
                            rand = Math.random();
                            childPopulation[p][j] = population[p][j] + Math.abs(step * rand);
                        } else if (population[p][j] > normMax[p]) {
                            rand = Math.random();
                            childPopulation[p][j] = population[p][j] - Math.abs(step * rand);
                        } else {
                            rand = Math.random();
                            double rand2 = Math.random();
                            if (rand2 < 0.5) {
                                childPopulation[p][j] = population[p][j] + factor * step * rand;
                            } else {
                                childPopulation[p][j] = population[p][j] - factor * step * rand;
                            }
                            //越界处理
                            if (childPopulation[p][j] < domainMin[p]) {
                                childPopulation[p][j] = domainMax[p] - (domainMin[p] - childPopulation[p][j]);
                            } else if (childPopulation[p][j] > domainMax[p]) {
                                childPopulation[p][j] = domainMin[p] + (childPopulation[p][j] - domainMax[p]);
                            }
                        }
                    } else {
                        if (population[p][j] < normMin[p]) {
                            rand = Math.random();
                            childPopulation[p][j] = population[p][j] + (normMin[p] - population[p][j]) * rand;
                        } else if (population[p][j] > normMax[p]) {
                            rand = Math.random();
                            childPopulation[p][j] = population[p][j] - (population[p][j] - normMax[p]) * rand;
                        } else {
                            childPopulation[p][j] = population[p][j];
                            //越界处理
                            if (childPopulation[p][j] < domainMin[p]) {
                                childPopulation[p][j] = domainMax[p] - (domainMin[p] - childPopulation[p][j]);
                            } else if (childPopulation[p][j] > domainMax[p]) {
                                childPopulation[p][j] = domainMin[p] + (childPopulation[p][j] - domainMax[p]);
                            }
                        }
                    }
                }
                for (int j = 0; j < 2 * populationSize; j++) {
                    if (j < populationSize) {
                        bothPopulation[p][j] = population[p][j];
                    } else {
                        bothPopulation[p][j] = childPopulation[p][j - populationSize];
                    }
                }

                for (int k = 0; k < 2 * populationSize; k++) {
                    int win = 0;
                    Set<Integer> set = new HashSet<>();
                    while (set.size() < votePopulationSize) {
                        set.add((int) Math.round(Math.floor(Math.random() * 2 * populationSize)));
                    }
                    Iterator<Integer> it = set.iterator();
                    for (int j = 0; j < votePopulationSize; j++) {
                        int inte = it.next();
                        if (func(bothPopulation, k) < func(bothPopulation, inte)) {
                            win++;
                        }
                    }
                    winNum[p][k] = win;
                }

                sort(bothPopulation, winNum, variableNum);

                for (int l = 0; l < populationSize; l++) {
                    population[p][l] = bothPopulation[p][l];
                }

                situation[p] = population[p][0];
                for (int h = 0; h < populationSize * acceptRate; h++) {
                    if (func(population, h) < func(population, 0)) {
                        situation[p] = population[p][h];
                    }
                    if (population[p][h] < normMin[p] || func(population, h) < L[p]) {
                        normMin[p] = population[p][h];
                        L[p] = func(population, h);
                    }
                    if (population[p][h] > normMax[p] || func(population, h) < U[p]) {
                        normMax[p] = population[p][h];
                        U[p] = func(population, h);
                    }
                }

                System.out.println(String.format("*************第%s代，第%s个参数************", currentGeneration, p));
                for (int j = 0; j < populationSize; j++) {
                    System.out.println(population[p][j] + " " + func(population, j));
                }
                System.out.println();
            }
        }
    }

    //种群初始化函数
    private void initPopulation(double population[][], double domainMin[], double domainMax[]) {
        for (int k = 0; k < population.length; k++) {
            for (int j = 0; j < population[k].length; j++) {
                population[k][j] = domainMin[k] + (domainMax[k] - domainMin[k]) * Math.random();
            }
        }
    }

    //信仰空间初始化函数
    private void initBelief(double situation[], double population[][]) {
        for (int k = 0; k < population.length; k++) {
            situation[k] = population[k][0];
            for (int j = 0; j < population[k].length; j++) {
                if (func(population, j) < situation[k])
                    situation[k] = population[k][j];
            }
        }
    }

    // 适应值函数
    private double func(double x, double y) {
        double z;
        //z = 20 + x * x - 10 * Math.cos(2 * Math.PI * x) + y * y - 10 * Math.cos(2 * Math.PI * y);
        //z=(1+(x+y+1)*(x+y+1)*(19-14*x+3*x*x-14*y+3*y*y+6*x*y))*
        //(30+(2*x-3*y)*(2*x-3*y)*(18-32*x+12*x*x+48*y+27*y*y-36*x*y));
        z = (x * x + y - 11) * (x * x + y - 11) + (x + y * y - 7);  //-10.3279
        /*z = 4 * Math.pow(x, 2) - 2.1 * Math.pow(x, 4) +
                Math.pow(x, 6) / 3 + x * y - 4 * Math.pow(y, 2) + 4 * Math.pow(y, 4);*/
        //z=(double)(-1/(Math.pow(x,2)+Math.pow(y,2)));
        return z;
    }

    private double func(double[][] arr, int j) {
        double x = arr[0][j];
        double y = arr[1][j];
        return func(x, y);
    }

    private void sort(double[][] pop, int winnum[][], int paraSize) {
        for (int p = 0; p < paraSize; p++) {
            for (int i = 0; i < pop[p].length; i++) {
                for (int j = (i + 1); j < pop[p].length; j++) {
                    if (winnum[p][i] < winnum[p][j]) {
                        double tempD = pop[p][i];
                        pop[p][i] = pop[p][j];
                        pop[p][j] = tempD;
                        int tempI = winnum[p][i];
                        winnum[p][i] = winnum[p][j];
                        winnum[p][j] = tempI;
                    }
                }
            }
        }
    }

    public double[][] getPopulation() {
        return population;
    }

    public int getCurrentGeneration() {
        return currentGeneration;
    }

    public int getPopulationSize() {
        return populationSize;
    }
}